#include "klib.h"
#include "types.h"
#include "log.h"
#include "cpu_instr.h"
#include "memory.h"

void kernel_strcpy(char* dest, const char* src)
{
    if (!dest || !src) return ;
    while (*dest && *src) *dest++ = *src++;
    *dest = '\0';
}

void kernel_strncpy(char* dest, const char* src, int size)
{
    if (!dest || !src || !size) return ;
    char* d = dest;
    const char* s = src;
    while ((size--) && *s) *d++ = *s++;
    if (size == 0) *(d - 1) = '\0';
    else *d = '\0';
}

int kernel_strncmp(const char* s1, const char* s2, int size)
{
    if (!s1 || !s2 || !size) return 1;
    while (*s1 && *s2 && (*s1++ == *s2++) && size--);
    return !(*s1 == '\0' || *s2 == '\0' || *s1 == *s2);
}

int kernel_strlen(const char* str)
{
    if (!str) return 0;
    const char* s = str;
    int len = 0;
    while (*s++ && ++len);
    return len;
}

void kernel_memcpy(void* dest, void* src, int size)
{
    if (!dest || !src || !size) return ;
    uint8_t *s = (uint8_t*)src, *d = (uint8_t*)dest;
    while (size--) *d++ = *s++;
}

void kernel_memset(void* dest, uint8_t v, int size)
{
    if (!dest || !size) return ;
    uint8_t* d = (uint8_t*)dest;
    while (size--) *d++ = v;
}

int kernel_memcmp(void* d1, void* d2, int size)
{
    if (!d1 || !d2 || !size) return 1;
    uint8_t *p_d1 = (uint8_t*)d1, *p_d2 = (uint8_t*)d2;
    while (size--) if (*p_d1++ != *p_d2++) return 1;
    return 0;
}

void kernel_itoa(char* buf, int num, int base)
{
    static const char* num2ch = "fedcba9876543210123456789abcdef";
    char* p = buf;
    int old_num = num;

    if (base != 2 && base != 8 && base != 10 && base != 16) {
        *p = '\0';
        return ;
    }

    int signed_num = 0;

    if (num < 0 && base == 10) {
        *p++ = '-';
        signed_num = 1;
    }

    if (signed_num) {
        do {
            char ch = num2ch[num % base + 15];
            *p++ = ch;
            num /= base;
        } while (num);
    } else {
        uint32_t u_num = (uint32_t)num;
        do {
            char ch = num2ch[u_num % base + 15];
            *p++ = ch;
            u_num /= base;
        } while (u_num);
    }
    *p-- = '\0';
    char* start = !signed_num ? buf : buf + 1;

    while (start < p) {
        char ch = *start;
        *start++ = *p;
        *p-- = ch;
    }
}

void kernel_sprintf(char* buf, const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    kernel_vsprintf(buf, fmt, args);
    va_end(args);
}

void kernel_vsprintf(char* buf, const char* fmt, va_list args)
{
    enum { NORMAL, READ_FMT } state = NORMAL;
    char* cur = buf;
    char ch;
    while (ch = *fmt++) {
        switch (state) {
            case NORMAL:
                if (ch == '%') state = READ_FMT;
                else *cur++ = ch;
                break;
            case READ_FMT:
                if (ch == 's') {
                    const char* str = va_arg(args, char*);
                    int len = kernel_strlen(str);
                    while (len--) *cur++ = *str++;
                } else if (ch == 'd') {
                    int num = va_arg(args, int);
                    kernel_itoa(cur, num, 10);
                    cur += kernel_strlen(cur);
                } else if (ch == 'x') {
                    int num = va_arg(args, int);
                    kernel_itoa(cur, num, 16);
                    cur += kernel_strlen(cur);
                } else if (ch == 'c') {
                    char c = va_arg(args, int);
                    *cur++ = c;
                }
                state = NORMAL;
                break;
        }
    }
}

void pannic(const char* file, int line, const char* func, const char* cond)
{
    log_printf("assert failed! %s", cond);
    log_printf("file: %s\nline %d\nfunc: %s\n", file, line, func);
    for ( ; ; ) {
        hlt();
    }
}

char* get_file_name(const char* name)
{
    char* p = (char*)name;
    while (*p != '\0')  ++p;
    while (p >= name && *p != '/' && *p != '\\') --p;
    return p + 1;
}

int strings_count(char** start)
{
    int count = 0;
    if (!start) return 0;
    while (*start++)  ++count;
    return count;
}
